package org.checkerframework.checker.units;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.Tree;
import java.io.File;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
import org.checkerframework.checker.units.qual.MixedUnits;
import org.checkerframework.checker.units.qual.Prefix;
import org.checkerframework.checker.units.qual.UnitsBottom;
import org.checkerframework.checker.units.qual.UnitsMultiple;
import org.checkerframework.checker.units.qual.UnknownUnits;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotationClassLoader;
import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.PropagationTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.DefaultQualifierKindHierarchy;
import org.checkerframework.framework.util.QualifierKind;
import org.checkerframework.framework.util.QualifierKindHierarchy;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypeSystemError;
import org.checkerframework.javacutil.UserError;
import org.plumelib.reflection.Signatures;

/**
 * Annotated type factory for the Units Checker.
 *
 * <p>Handles multiple names for the same unit, with different prefixes, e.g. @kg is the same
 * as @g(Prefix.kilo).
 *
 * <p>Supports relations between units. If {@code m} is a variable of type "@m" and {@code s} is a
 * variable of type "@s", the division {@code m / s} is automatically annotated as "@mPERs", the
 * correct unit for the result.
 */
public class UnitsAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
  private static final Class<org.checkerframework.checker.units.qual.UnitsRelations>
      unitsRelationsAnnoClass = org.checkerframework.checker.units.qual.UnitsRelations.class;

  protected final AnnotationMirror mixedUnits =
      AnnotationBuilder.fromClass(elements, MixedUnits.class);
  protected final AnnotationMirror TOP = AnnotationBuilder.fromClass(elements, UnknownUnits.class);
  protected final AnnotationMirror BOTTOM =
      AnnotationBuilder.fromClass(elements, UnitsBottom.class);

  /** The UnitsMultiple.prefix argument/element. */
  private final ExecutableElement unitsMultiplePrefixElement =
      TreeUtils.getMethod(UnitsMultiple.class, "prefix", 0, processingEnv);

  /** The UnitsMultiple.quantity argument/element. */
  private final ExecutableElement unitsMultipleQuantityElement =
      TreeUtils.getMethod(UnitsMultiple.class, "quantity", 0, processingEnv);

  /** The UnitsRelations.value argument/element. */
  private final ExecutableElement unitsRelationsValueElement =
      TreeUtils.getMethod(
          org.checkerframework.checker.units.qual.UnitsRelations.class, "value", 0, processingEnv);

  /**
   * Map from canonical class name to the corresponding UnitsRelations instance. We use the string
   * to prevent instantiating the UnitsRelations multiple times.
   */
  private @MonotonicNonNull Map<@CanonicalName String, UnitsRelations> unitsRel;

  /** Map from canonical name of external qualifiers, to their Class. */
  private static final Map<@CanonicalName String, Class<? extends Annotation>> externalQualsMap =
      new HashMap<>();

  private static final Map<String, AnnotationMirror> aliasMap = new HashMap<>();

  @SuppressWarnings("this-escape")
  public UnitsAnnotatedTypeFactory(BaseTypeChecker checker) {
    // use true to enable flow inference, false to disable it
    super(checker, false);

    this.postInit();
  }

  // In Units Checker, we always want to print out the Invisible Qualifiers (UnknownUnits), and to
  // format the print out of qualifiers by removing Prefix.one
  @Override
  protected AnnotatedTypeFormatter createAnnotatedTypeFormatter() {
    return new UnitsAnnotatedTypeFormatter(checker);
  }

  // Converts all metric-prefixed units' alias annotations (eg @kg) into base unit annotations
  // with prefix values (eg @g(Prefix.kilo))
  @Override
  public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) {
    // Get the name of the aliased annotation
    String aname = AnnotationUtils.annotationName(anno);

    // See if we already have a map from this aliased annotation to its corresponding base unit
    // annotation
    if (aliasMap.containsKey(aname)) {
      // if so return it
      return aliasMap.get(aname);
    }

    boolean built = false;
    AnnotationMirror result = null;
    // if not, look for the UnitsMultiple meta annotations of this aliased annotation
    for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) {
      // see if the meta annotation is UnitsMultiple
      if (isUnitsMultiple(metaAnno)) {
        // retrieve the Class of the base unit annotation
        Name baseUnitAnnoClass =
            AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement);

        // retrieve the SI Prefix of the aliased annotation
        Prefix prefix =
            AnnotationUtils.getElementValueEnum(
                metaAnno, unitsMultiplePrefixElement, Prefix.class, Prefix.one);

        // Build a base unit annotation with the prefix applied
        result =
            UnitsRelationsTools.buildAnnoMirrorWithSpecificPrefix(
                processingEnv, baseUnitAnnoClass, prefix);

        // TODO: assert that this annotation is a prefix multiple of a Unit that's in the
        // supported type qualifiers list currently this breaks for externally loaded
        // annotations if the order was an alias before a base annotation.
        // assert isSupportedQualifier(result);

        built = true;
        break;
      }
    }

    if (built) {
      // aliases shouldn't have Prefix.one, but if it does then clean it up here
      if (UnitsRelationsTools.getPrefix(result) == Prefix.one) {
        result = removePrefix(result);
      }

      // add this to the alias map
      aliasMap.put(aname, result);
      return result;
    }

    return super.canonicalAnnotation(anno);
  }

  /**
   * Returns a map from canonical class name to the corresponding UnitsRelations instance.
   *
   * @return a map from canonical class name to the corresponding UnitsRelations instance
   */
  protected Map<@CanonicalName String, UnitsRelations> getUnitsRel() {
    if (unitsRel == null) {
      unitsRel = new HashMap<>();
      // Always add the default units relations, for the standard units.
      // Other code adds more relations.
      unitsRel.put(
          UnitsRelationsDefault.class.getCanonicalName(),
          new UnitsRelationsDefault().init(processingEnv));
    }
    return unitsRel;
  }

  @Override
  protected AnnotationClassLoader createAnnotationClassLoader() {
    // Use the UnitsAnnotationClassLoader instead of the default one
    return new UnitsAnnotationClassLoader(checker);
  }

  @Override
  protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
    // Get all the loaded annotations.
    Set<Class<? extends Annotation>> qualSet = getBundledTypeQualifiers();

    // Load all the units specified on the command line.
    loadAllExternalUnits();
    qualSet.addAll(externalQualsMap.values());

    return qualSet;
  }

  /** Loads all the externnal units specified on the command line. */
  private void loadAllExternalUnits() {
    // load external individually named units
    for (String qualName : checker.getStringsOption("units", ',')) {
      if (!Signatures.isBinaryName(qualName)) {
        throw new UserError("Malformed qualifier name \"%s\" in -Aunits", qualName);
      }
      loadExternalUnit(qualName);
    }

    // load external directories of units
    for (String directoryName : checker.getStringsOption("unitsDirs", ':')) {
      if (!new File(directoryName).exists()) {
        throw new UserError("Nonexistent directory in -AunitsDirs: " + directoryName);
      }
      loadExternalDirectory(directoryName);
    }
  }

  /**
   * Loads and processes a single external units qualifier.
   *
   * @param annoName the name of a units qualifier
   */
  private void loadExternalUnit(@BinaryName String annoName) {
    // loadExternalAnnotationClass() returns null for alias units
    Class<? extends Annotation> loadedClass = loader.loadExternalAnnotationClass(annoName);
    if (loadedClass != null) {
      addUnitToExternalQualMap(loadedClass);
    }
  }

  /** Loads and processes the units qualifiers from a single external directory. */
  private void loadExternalDirectory(String directoryName) {
    Set<Class<? extends Annotation>> annoClassSet =
        loader.loadExternalAnnotationClassesFromDirectory(directoryName);

    for (Class<? extends Annotation> annoClass : annoClassSet) {
      addUnitToExternalQualMap(annoClass);
    }
  }

  /** Adds the annotation class to the external qualifier map if it is not an alias annotation. */
  private void addUnitToExternalQualMap(Class<? extends Annotation> annoClass) {
    AnnotationMirror mirror =
        UnitsRelationsTools.buildAnnoMirrorWithNoPrefix(
            processingEnv, annoClass.getCanonicalName());

    // if it is not an aliased annotation, add to external quals map if it isn't already in map
    if (!isAliasedAnnotation(mirror)) {
      String unitClassName = annoClass.getCanonicalName();
      if (!externalQualsMap.containsKey(unitClassName)) {
        externalQualsMap.put(unitClassName, annoClass);
      }
    }
    // if it is an aliased annotation
    else {
      // ensure it has a base unit
      @CanonicalName Name baseUnitClass = getBaseUnitAnno(mirror);
      if (baseUnitClass != null) {
        // if the base unit isn't already added, add that first
        @SuppressWarnings("signature") // https://tinyurl.com/cfissue/658
        @DotSeparatedIdentifiers String baseUnitClassName = baseUnitClass.toString();
        if (!externalQualsMap.containsKey(baseUnitClassName)) {
          loadExternalUnit(baseUnitClassName);
        }

        // then add the aliased annotation to the alias map
        // TODO: refactor so we can directly add to alias map, skipping the assert check in
        // canonicalAnnotation.
        canonicalAnnotation(mirror);
      } else {
        // error: somehow the aliased annotation has @UnitsMultiple meta annotation, but no
        // base class defined in that meta annotation
        // TODO: error abort
      }
    }

    // process the units annotation and add its corresponding units relations class
    addUnitsRelations(annoClass);
  }

  private boolean isAliasedAnnotation(AnnotationMirror anno) {
    // loop through the meta annotations of the annotation, look for UnitsMultiple
    for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) {
      // see if the meta annotation is UnitsMultiple
      if (isUnitsMultiple(metaAnno)) {
        // TODO: does every alias have to have Prefix?
        return true;
      }
    }

    // if we are unable to find UnitsMultiple meta annotation, then this is not an Aliased
    // Annotation
    return false;
  }

  /**
   * Returns the name of the given annotation, if it is meta-annotated with UnitsMultiple; otherwise
   * return null.
   *
   * @param anno the annotation to examine
   * @return the annotation's name, if it is meta-annotated with UnitsMultiple; otherwise null
   */
  private @Nullable @CanonicalName Name getBaseUnitAnno(AnnotationMirror anno) {
    // loop through the meta annotations of the annotation, look for UnitsMultiple
    for (AnnotationMirror metaAnno : anno.getAnnotationType().asElement().getAnnotationMirrors()) {
      // see if the meta annotation is UnitsMultiple
      if (isUnitsMultiple(metaAnno)) {
        // TODO: does every alias have to have Prefix?
        // Retrieve the base unit annotation.
        Name baseUnitAnnoClass =
            AnnotationUtils.getElementValueClassName(metaAnno, unitsMultipleQuantityElement);
        return baseUnitAnnoClass;
      }
    }
    return null;
  }

  /**
   * Returns true if {@code metaAnno} is {@link UnitsMultiple}.
   *
   * @param metaAnno an annotation mirror
   * @return true if {@code metaAnno} is {@link UnitsMultiple}
   */
  private boolean isUnitsMultiple(AnnotationMirror metaAnno) {
    return areSameByClass(metaAnno, UnitsMultiple.class);
  }

  /** A class loader for looking up annotations. */
  private static final ClassLoader CLASSLOADER =
      InternalUtils.getClassLoaderForClass(AnnotationUtils.class);

  /**
   * Look for an @UnitsRelations annotation on the qualifier and add it to the list of
   * UnitsRelations.
   *
   * @param qual the qualifier to investigate
   */
  private void addUnitsRelations(Class<? extends Annotation> qual) {
    AnnotationMirror am = AnnotationBuilder.fromClass(elements, qual);

    for (AnnotationMirror ama : am.getAnnotationType().asElement().getAnnotationMirrors()) {
      if (areSameByClass(ama, unitsRelationsAnnoClass)) {
        String theclassname =
            AnnotationUtils.getElementValueClassName(ama, unitsRelationsValueElement).toString();
        if (!Signatures.isClassGetName(theclassname)) {
          throw new UserError(
              "Malformed class name \"%s\" should be in ClassGetName format in annotation %s",
              theclassname, ama);
        }
        Class<?> valueElement;
        try {
          valueElement = Class.forName(theclassname, true, CLASSLOADER);
        } catch (ClassNotFoundException e) {
          String msg =
              String.format(
                  "Could not load class '%s' for field 'value' in annotation %s",
                  theclassname, ama);
          throw new UserError(msg, e);
        }
        Class<? extends UnitsRelations> unitsRelationsClass;
        try {
          unitsRelationsClass = valueElement.asSubclass(UnitsRelations.class);
        } catch (ClassCastException ex) {
          throw new UserError(
              "Invalid @UnitsRelations meta-annotation found in %s. "
                  + "@UnitsRelations value %s is not a subclass of "
                  + "org.checkerframework.checker.units.UnitsRelations.",
              qual, ama);
        }
        String classname = unitsRelationsClass.getCanonicalName();

        if (!getUnitsRel().containsKey(classname)) {
          try {
            unitsRel.put(
                classname,
                unitsRelationsClass.getDeclaredConstructor().newInstance().init(processingEnv));
          } catch (Throwable e) {
            throw new TypeSystemError("Throwable when instantiating UnitsRelations", e);
          }
        }
      }
    }
  }

  @Override
  public TreeAnnotator createTreeAnnotator() {
    // Don't call super.createTreeAnnotator() because it includes PropagationTreeAnnotator,
    // but we want to use UnitsPropagationTreeAnnotator instead.
    return new ListTreeAnnotator(
        new UnitsPropagationTreeAnnotator(this),
        new LiteralTreeAnnotator(this).addStandardLiteralQualifiers(),
        new UnitsTreeAnnotator(this));
  }

  private static class UnitsPropagationTreeAnnotator extends PropagationTreeAnnotator {

    public UnitsPropagationTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
      super(atypeFactory);
    }

    // Handled completely by UnitsTreeAnnotator
    @Override
    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
      return null;
    }

    // Handled completely by UnitsTreeAnnotator
    @Override
    public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) {
      return null;
    }
  }

  /** A class for adding annotations based on tree. */
  private class UnitsTreeAnnotator extends TreeAnnotator {

    /**
     * Creates a new UnitsTreeAnnotator.
     *
     * @param atypeFactory the type factory
     */
    UnitsTreeAnnotator(UnitsAnnotatedTypeFactory atypeFactory) {
      super(atypeFactory);
    }

    @SuppressWarnings("DuplicateBranches")
    @Override
    public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
      AnnotatedTypeMirror lht = getAnnotatedType(tree.getLeftOperand());
      AnnotatedTypeMirror rht = getAnnotatedType(tree.getRightOperand());
      Tree.Kind kind = tree.getKind();

      // Remove Prefix.one
      if (UnitsRelationsTools.getPrefix(lht) == Prefix.one) {
        lht = UnitsRelationsTools.removePrefix(elements, lht);
      }
      if (UnitsRelationsTools.getPrefix(rht) == Prefix.one) {
        rht = UnitsRelationsTools.removePrefix(elements, rht);
      }

      AnnotationMirror bestres = null;
      for (UnitsRelations ur : getUnitsRel().values()) {
        AnnotationMirror res = useUnitsRelation(kind, ur, lht, rht);

        if (bestres != null && res != null && !bestres.equals(res)) {
          checker.message(
              Diagnostic.Kind.WARNING,
              "UnitsRelation mismatch, taking neither! Previous: "
                  + bestres
                  + " and current: "
                  + res);
          return null; // super.visitBinary(tree, type);
        }

        if (res != null) {
          bestres = res;
        }
      }

      if (bestres != null) {
        type.replaceAnnotation(bestres);
      } else {
        // If none of the units relations classes could resolve the units, then apply
        // default rules.

        switch (kind) {
          case MINUS:
          case PLUS:
            if (lht.getPrimaryAnnotations().equals(rht.getPrimaryAnnotations())) {
              // The sum or difference has the same units as both operands.
              type.replaceAnnotations(lht.getPrimaryAnnotations());
            } else {
              // otherwise it results in mixed
              type.replaceAnnotation(mixedUnits);
            }
            break;
          case DIVIDE:
            if (lht.getPrimaryAnnotations().equals(rht.getPrimaryAnnotations())) {
              // If the units of the division match, return TOP
              type.replaceAnnotation(TOP);
            } else if (UnitsRelationsTools.hasNoUnits(rht)) {
              // any unit divided by a scalar keeps that unit
              type.replaceAnnotations(lht.getPrimaryAnnotations());
            } else if (UnitsRelationsTools.hasNoUnits(lht)) {
              // scalar divided by any unit returns mixed
              type.replaceAnnotation(mixedUnits);
            } else {
              // else it is a division of two units that have no defined relations
              // from a relations class
              // return mixed
              type.replaceAnnotation(mixedUnits);
            }
            break;
          case MULTIPLY:
            if (UnitsRelationsTools.hasNoUnits(lht)) {
              // any unit multiplied by a scalar keeps the unit
              type.replaceAnnotations(rht.getPrimaryAnnotations());
            } else if (UnitsRelationsTools.hasNoUnits(rht)) {
              // any scalar multiplied by a unit becomes the unit
              type.replaceAnnotations(lht.getPrimaryAnnotations());
            } else {
              // else it is a multiplication of two units that have no defined
              // relations from a relations class
              // return mixed
              type.replaceAnnotation(mixedUnits);
            }
            break;
          case REMAINDER:
            // in modulo operation, it always returns the left unit regardless of what
            // it is (unknown, or some unit)
            type.replaceAnnotations(lht.getPrimaryAnnotations());
            break;
          default:
            // Placeholders for unhandled binary operations
            // Do nothing
        }
      }

      return null;
    }

    @Override
    public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMirror type) {
      ExpressionTree var = tree.getVariable();
      AnnotatedTypeMirror varType = getAnnotatedType(var);

      type.replaceAnnotations(varType.getPrimaryAnnotations());
      return null;
    }

    private @Nullable AnnotationMirror useUnitsRelation(
        Tree.Kind kind, UnitsRelations ur, AnnotatedTypeMirror lht, AnnotatedTypeMirror rht) {

      if (ur != null) {
        switch (kind) {
          case DIVIDE:
            return ur.division(lht, rht);
          case MULTIPLY:
            return ur.multiplication(lht, rht);
          default:
            // Do nothing
        }
      }
      return null;
    }
  }

  /** Set the Bottom qualifier as the bottom of the hierarchy. */
  @Override
  protected QualifierHierarchy createQualifierHierarchy() {
    return new UnitsQualifierHierarchy();
  }

  /** Qualifier Hierarchy for the Units Checker. */
  @AnnotatedFor("nullness")
  protected class UnitsQualifierHierarchy extends MostlyNoElementQualifierHierarchy {
    /** Constructor. */
    public UnitsQualifierHierarchy() {
      super(
          UnitsAnnotatedTypeFactory.this.getSupportedTypeQualifiers(),
          elements,
          UnitsAnnotatedTypeFactory.this);
    }

    @Override
    protected QualifierKindHierarchy createQualifierKindHierarchy(
        @UnderInitialization UnitsQualifierHierarchy this,
        Collection<Class<? extends Annotation>> qualifierClasses) {
      return new UnitsQualifierKindHierarchy(qualifierClasses, elements);
    }

    @Override
    protected boolean isSubtypeWithElements(
        AnnotationMirror subAnno,
        QualifierKind subKind,
        AnnotationMirror superAnno,
        QualifierKind superKind) {
      return AnnotationUtils.areSame(subAnno, superAnno);
    }

    @Override
    protected AnnotationMirror leastUpperBoundWithElements(
        AnnotationMirror a1,
        QualifierKind qualifierKind1,
        AnnotationMirror a2,
        QualifierKind qualifierKind2,
        QualifierKind lubKind) {
      if (qualifierKind1.isBottom()) {
        return a2;
      } else if (qualifierKind2.isBottom()) {
        return a1;
      } else if (qualifierKind1 == qualifierKind2) {
        if (AnnotationUtils.areSame(a1, a2)) {
          return a1;
        } else {
          @SuppressWarnings({
            "nullness:assignment" // Every qualifier kind is a
            // key in directSuperQualifierMap.
          })
          @NonNull AnnotationMirror lub =
              ((UnitsQualifierKindHierarchy) qualifierKindHierarchy)
                  .directSuperQualifierMap.get(qualifierKind1);
          return lub;
        }
      }
      throw new TypeSystemError("Unexpected QualifierKinds: %s %s", qualifierKind1, qualifierKind2);
    }

    @Override
    protected AnnotationMirror greatestLowerBoundWithElements(
        AnnotationMirror a1,
        QualifierKind qualifierKind1,
        AnnotationMirror a2,
        QualifierKind qualifierKind2,
        QualifierKind glbKind) {
      return UnitsAnnotatedTypeFactory.this.BOTTOM;
    }
  }

  /** UnitsQualifierKindHierarchy. */
  @AnnotatedFor("nullness")
  protected static class UnitsQualifierKindHierarchy extends DefaultQualifierKindHierarchy {

    /**
     * Mapping from QualifierKind to an AnnotationMirror that represents its direct super qualifier.
     * Every qualifier kind maps to a nonnull AnnotationMirror.
     */
    private final Map<QualifierKind, AnnotationMirror> directSuperQualifierMap;

    /**
     * Creates a UnitsQualifierKindHierarchy.
     *
     * @param qualifierClasses classes of annotations that are the qualifiers for this hierarchy
     * @param elements element utils
     */
    public UnitsQualifierKindHierarchy(
        Collection<Class<? extends Annotation>> qualifierClasses, Elements elements) {
      super(qualifierClasses, UnitsBottom.class);
      directSuperQualifierMap = createDirectSuperQualifierMap(elements);
    }

    /**
     * Creates the direct super qualifier map.
     *
     * @param elements element utils
     * @return the map
     */
    @RequiresNonNull("this.qualifierKinds")
    private Map<QualifierKind, AnnotationMirror> createDirectSuperQualifierMap(
        @UnderInitialization UnitsQualifierKindHierarchy this, Elements elements) {
      Map<QualifierKind, AnnotationMirror> directSuperType = new TreeMap<>();
      for (QualifierKind qualifierKind : qualifierKinds) {
        QualifierKind directSuperTypeKind = getDirectSuperQualifierKind(qualifierKind);
        AnnotationMirror directSuperTypeAnno;
        try {
          directSuperTypeAnno = AnnotationBuilder.fromName(elements, directSuperTypeKind.getName());
        } catch (BugInCF ex) {
          throw new TypeSystemError("Unit annotations must have a default for all elements.");
        }
        if (directSuperTypeAnno == null) {
          throw new TypeSystemError("Could not create AnnotationMirror: %s", directSuperTypeAnno);
        }
        directSuperType.put(qualifierKind, directSuperTypeAnno);
      }
      return directSuperType;
    }

    /**
     * Returns the direct super qualifier for the given qualifier kind.
     *
     * @param qualifierKind qualifier kind
     * @return direct super qualifier kind
     */
    private QualifierKind getDirectSuperQualifierKind(
        @UnderInitialization UnitsQualifierKindHierarchy this, QualifierKind qualifierKind) {
      if (qualifierKind.isTop()) {
        return qualifierKind;
      }
      Set<QualifierKind> superQuals = new TreeSet<>(qualifierKind.getStrictSuperTypes());
      while (!superQuals.isEmpty()) {
        Set<QualifierKind> lowest = findLowestQualifiers(superQuals);
        if (lowest.size() == 1) {
          return lowest.iterator().next();
        }
        superQuals.removeAll(lowest);
      }
      throw new TypeSystemError("No direct super qualifier found for %s", qualifierKind);
    }
  }

  private AnnotationMirror removePrefix(AnnotationMirror anno) {
    return UnitsRelationsTools.removePrefix(elements, anno);
  }
}
